Skip to content

fix: stop tearing down healthy HTTP sessions on transient probe misses (#1207)#1237

Open
Scriptwonder wants to merge 2 commits into
CoplayDev:betafrom
Scriptwonder:fix/issue-1207-orphan-detector-debounce
Open

fix: stop tearing down healthy HTTP sessions on transient probe misses (#1207)#1237
Scriptwonder wants to merge 2 commits into
CoplayDev:betafrom
Scriptwonder:fix/issue-1207-orphan-detector-debounce

Conversation

@Scriptwonder

@Scriptwonder Scriptwonder commented Jul 4, 2026

Copy link
Copy Markdown
Collaborator

Addresses the dominant churn source in #1207, following the fix order @dsarno proposed from the evidence bundle.

Problem

Two amplifiers turned ordinary reload/test boundaries into no_unity_session wedges:

  1. Hair-trigger orphan detector. McpConnectionSection.UpdateConnectionStatus ends an active session the moment one cached reachability reading is false — and that reading is a lone TCP connect with a 50 ms wait, cached 0.75 s. On a machine busy with a test run or domain reload the probe times out against a perfectly healthy server; the detector then force-stops the bridge (13 teardowns / 147 socket closures in one session in the macOS / HTTP transport: bridge WebSocket repeatedly drops (close 1005) on reload/test boundaries → no_unity_session #1207 evidence bundle), feeding the reconnect churn that outruns the server's session-resolve window.
  2. Neutered escape hatch. UNITY_MCP_SESSION_RESOLVE_MAX_WAIT_S is documented as configurable but was clamped to a ceiling equal to its default (20 s), so users whose reloads legitimately exceed 20 s could not actually extend the window.

Fix

  • Orphan teardown now requires 3 consecutive failed polls at the 0.75 s cadence (~2.25 s of continuous unreachability); readings taken while the editor is compiling/importing don't count, and detection is skipped while busy. The decision is an internal static predicate with tests.
  • Probe connect wait raised 50 ms → 250 ms, implemented as an overall budget shared across candidate hosts so the worst-case main-thread wait cannot multiply (a refused connect still fails in ~1 ms; the budget only bites when packets are dropped or the stack is starved).
  • UNITY_MCP_SESSION_RESOLVE_MAX_WAIT_S ceiling raised to 120 s (default unchanged at 20 s); UNITY_MCP_SESSION_READY_WAIT_SECONDS likewise (default 6 s). Both now share one bounded env-read helper with tests.

What this deliberately does not do

Verification

  • cd Server && uv run pytest tests/ -q1313 passed, 3 skipped (5 new env-clamp tests).
  • 7 new EditMode tests covering the teardown predicate matrix (threshold, below-threshold, busy-editor deferral, toggle-in-progress, non-HTTP-local, not-running).
  • Compile-only matrix via tools/check-unity-versions.sh (2021.3 floor passes locally).

Summary by CodeRabbit

  • Bug Fixes
    • Improved local server reachability probing by using a larger probe timeout and a single overall timeout budget across probe attempts.
    • Reduced unnecessary orphan-session shutdowns by requiring multiple consecutive “server down” observations before terminating, while respecting editor busy state and connection-toggle in progress.
    • Expanded and hardened wait-time configuration handling, clamping values safely up to 120 seconds and falling back on invalid inputs.
  • Tests
    • Added automated coverage for wait-time environment variable parsing and orphaned-session detection behavior.

CoplayDev#1207)

The orphaned-session detector ended an active session on a SINGLE stale
reachability reading, and the reading came from a lone 50ms TCP connect
cached for 0.75s — trivially false-negative on a machine busy with test
runs or domain reloads. Evidence bundles in CoplayDev#1207 show 13 teardowns and
147 socket closures in one session from exactly this loop, wedging the
bridge in no_unity_session churn until manual recovery.

- Require 3 consecutive failed polls (0.75s cadence) before declaring a
  session orphaned; probe readings taken while the editor is compiling or
  importing don't count toward teardown, and detection is skipped entirely
  while busy.
- Raise the probe's connect wait 50ms -> 250ms, as an overall budget shared
  across candidate hosts so the worst-case main-thread wait cannot multiply.
- Honor UNITY_MCP_SESSION_RESOLVE_MAX_WAIT_S above 20s (ceiling now 120s;
  default unchanged): the old ceiling equalled the default, silently
  neutering the documented escape hatch for projects whose reloads or test
  boundaries legitimately exceed 20s. Same treatment for
  UNITY_MCP_SESSION_READY_WAIT_SECONDS, and both now share one bounded
  env-read helper.

The remaining piece of CoplayDev#1207 (keepalive reload-awareness in
WebSocketTransportClient) is untouched here: the resume machinery reworked
in CoplayDev#1234 already covers reload boundaries, and the detector debounce
removes the dominant churn source dsarno identified.
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dd56a69c-ce48-40ef-ad50-153258977f89

📥 Commits

Reviewing files that changed from the base of the PR and between 18158fa and 2c8ae3e.

📒 Files selected for processing (1)
  • MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs
🚧 Files skipped from review as they are similar to previous changes (1)
  • MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs

📝 Walkthrough

Walkthrough

Unity editor local HTTP reachability probing now uses a longer shared timeout budget, and orphaned session teardown waits for repeated down probes. Server-side wait-time environment parsing is centralized with a higher clamp ceiling and new tests.

Changes

Unity Editor: Reachability probing and orphan-session teardown

Layer / File(s) Summary
Local port reachability timeout budget
MCPForUnity/Editor/Services/ServerManagementService.cs
Increases the overall probe timeout from 50ms to 250ms and changes TryConnectToLocalPort to treat the timeout as a shared budget across all candidate hosts.
Orphan-session teardown policy
MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs, TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/McpConnectionSectionOrphanDetectionTests.cs, TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/McpConnectionSectionOrphanDetectionTests.cs.meta
Adds consecutiveServerDownPolls, OrphanedSessionDownPollThreshold, and ShouldEndOrphanedSession; wires the policy into connection status and HTTP polling updates; adds NUnit coverage for threshold, busy-editor, toggle-in-progress, transport, and session-running cases.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Server-side bounded wait-time environment handling

Layer / File(s) Summary
Bounded env wait-time helper and call sites
Server/src/transport/plugin_hub.py, Server/tests/test_plugin_hub_wait_env.py
Adds _read_bounded_wait_env to parse, warn on invalid values, and clamp environment-driven wait times; applies it in _resolve_session_id and the readiness fast-path in send_command_for_instance, raising the clamp ceiling from 20s to 120s; adds pytest tests validating default, override, ceiling clamp, and negative-value clamp behaviors.

Estimated code review effort: 2 (Simple) | ~15 minutes

Sequence Diagram(s)

sequenceDiagram
  participant UpdateStartHttpButtonState
  participant UpdateConnectionStatus
  participant ShouldEndOrphanedSession

  UpdateStartHttpButtonState->>UpdateStartHttpButtonState: probe local HTTP server reachability
  UpdateStartHttpButtonState->>UpdateStartHttpButtonState: increment/reset consecutiveServerDownPolls
  UpdateConnectionStatus->>ShouldEndOrphanedSession: check editorBusy, toggleInProgress, downPollCount
  ShouldEndOrphanedSession-->>UpdateConnectionStatus: return true/false
  UpdateConnectionStatus->>UpdateConnectionStatus: end session if true
Loading

Possibly related PRs

  • CoplayDev/unity-mcp#510: Both modify _resolve_session_id and the readiness fast-path in plugin_hub.py to use bounded environment-variable wait times.
  • CoplayDev/unity-mcp#517: Both change McpConnectionSection's orphaned HTTP local session teardown logic.
  • CoplayDev/unity-mcp#556: Extends the same IsLocalHttpServerReachable/TryConnectToLocalPort probe logic this PR further refines.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main fix: reducing false orphan teardown from transient HTTP probe misses.
Description check ✅ Passed The description covers the problem, fix, and verification, but it omits several template sections like Type of Change and Compatibility.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Scriptwonder Scriptwonder added the bug Something isn't working label Jul 4, 2026
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 91.66667% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
Server/src/transport/plugin_hub.py 91.66% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@Scriptwonder Scriptwonder added the safe-to-test Triggers CI checks label Jul 4, 2026
@Scriptwonder

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs (1)

342-366: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reset the orphaned-poll counter when the session starts
consecutiveServerDownPolls only clears after UpdateStartHttpButtonState() sees the server up again, so a stale count can survive a successful StartAsync() and immediately satisfy orphan detection before the next fresh probe. Clear it when the session transitions to running.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs`
around lines 342 - 366, The orphaned-session detection in UpdateConnectionStatus
can be triggered by a stale consecutiveServerDownPolls count after the session
transitions to running. Reset consecutiveServerDownPolls when the connection
successfully starts, and make sure the reset happens in the start-path tied to
StartAsync/UpdateStartHttpButtonState so ShouldEndOrphanedSession only uses
fresh probes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs`:
- Around line 342-366: The orphaned-session detection in UpdateConnectionStatus
can be triggered by a stale consecutiveServerDownPolls count after the session
transitions to running. Reset consecutiveServerDownPolls when the connection
successfully starts, and make sure the reset happens in the start-path tied to
StartAsync/UpdateStartHttpButtonState so ShouldEndOrphanedSession only uses
fresh probes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 39ada2ce-19ef-4b3b-955c-1851ab24acdd

📥 Commits

Reviewing files that changed from the base of the PR and between d87ca19 and 18158fa.

📒 Files selected for processing (6)
  • MCPForUnity/Editor/Services/ServerManagementService.cs
  • MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs
  • Server/src/transport/plugin_hub.py
  • Server/tests/test_plugin_hub_wait_env.py
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/McpConnectionSectionOrphanDetectionTests.cs
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/McpConnectionSectionOrphanDetectionTests.cs.meta

Review follow-up: a streak accumulated while no session was running (detector
inert, counter still counting) survived into a freshly started session and
could satisfy orphan detection before the first post-start probe refreshed.
Reset on the not-running -> running transition, which covers every start path
(manual Connect, auto-start, resume) since UpdateConnectionStatus runs on the
UI tick.
@Scriptwonder Scriptwonder marked this pull request as ready for review July 7, 2026 00:30
Copilot AI review requested due to automatic review settings July 7, 2026 00:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR reduces unnecessary HTTP transport session churn by debouncing the Unity-side “orphaned session” teardown logic, making local reachability probing less hair-trigger, and by allowing the server-side session resolve/ready wait env overrides to extend beyond the previous hard 20s ceiling.

Changes:

  • Debounce orphaned-session teardown to require 3 consecutive failed reachability polls, and skip/defer teardown while the editor is busy (compile/import) or a toggle is in progress.
  • Increase local HTTP reachability probe connect budget (50ms → 250ms) and share that budget across candidate hosts to cap worst-case UI-thread blocking.
  • Add a bounded env-read helper for PluginHub waits and extend max clamp to 120s, with new unit tests.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/McpConnectionSectionOrphanDetectionTests.cs.meta Adds Unity asset metadata for the new EditMode regression test.
TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/McpConnectionSectionOrphanDetectionTests.cs Adds EditMode regression tests for the orphaned-session teardown predicate.
Server/tests/test_plugin_hub_wait_env.py Adds tests for bounded/clamped env overrides for session resolve waits.
Server/src/transport/plugin_hub.py Introduces shared bounded env parsing for resolve/ready waits and raises the clamp ceiling.
MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs Implements debounced orphan detection with busy-editor deferral and a testable predicate.
MCPForUnity/Editor/Services/ServerManagementService.cs Increases local probe timeout and applies a shared overall timeout budget across probe hosts.
Files not reviewed (1)
  • TestProjects/UnityMCPTests/Assets/Tests/EditMode/Windows/McpConnectionSectionOrphanDetectionTests.cs.meta: Generated file

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +49 to +54
try:
value = float(raw)
except ValueError as e:
logger.warning("Invalid %s=%r, using default %s: %s", name, raw, default_s, e)
value = default_s
return max(0.0, min(value, max_s))
Comment on lines +33 to +35
def test_invalid_falls_back_to_default(monkeypatch):
monkeypatch.setenv(ENV, "not-a-number")
assert _read_bounded_wait_env(ENV, default_s=20.0, max_s=120.0) == 20.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working safe-to-test Triggers CI checks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants